package com.example.paging3samples.network

import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import com.example.paging3samples.database.PostDatabase
import com.example.paging3samples.database.mapper.toPostEntity
import com.example.paging3samples.database.model.PostEntity
import com.example.paging3samples.database.model.RemoteKeys
import com.example.paging3samples.database.repository.PostRepository
import com.example.paging3samples.network.model.anime.Post
import java.io.IOException
import java.io.InvalidObjectException
import javax.inject.Inject
import retrofit2.HttpException

/**
 * author: LiLiang
 * createTime: 2022/9/15-13:00
 * remark:
 **/
@OptIn(ExperimentalPagingApi::class)
class PostRemoteMediator(private val apiService: ApiService, private val db: PostDatabase): RemoteMediator<Int, PostEntity>() {

    override suspend fun load(loadType: LoadType, state: PagingState<Int, PostEntity>): MediatorResult {
        val page = when (val pageKeyData = getKeyPageData(loadType, state)) {
            is MediatorResult.Success -> {
                return pageKeyData
            }
            else -> {
                pageKeyData as Int
            }
        }

        try {
            val response = apiService.getPosts(page).posts
            val isEndOfList = response.isEmpty()
            db.withTransaction {
                if (loadType == LoadType.REFRESH) {
                    db.remoteKeysDao.clearRemoteKeys()
                    db.postDao.removePostAll()
                }
                val preKey = if (page == 0) null else page -1
                val nextKey = if (isEndOfList) null else page + 1
                val keys = response.map { post ->
                    RemoteKeys(repoId = post.id, prevKey = preKey, nextKey = nextKey)
                }
                db.remoteKeysDao.insertAll(keys)
                db.postDao.insertPosts(response.map { it.toPostEntity() })
            }
            return MediatorResult.Success(endOfPaginationReached = isEndOfList)
        } catch (e: IOException) {
            return MediatorResult.Error(e)
        } catch (e: HttpException) {
            return MediatorResult.Error(e)
        }
    }

    private suspend fun getKeyPageData(loadType: LoadType, state: PagingState<Int, PostEntity>): Any? {
        return when (loadType) {
            LoadType.REFRESH -> {
                val remoteKeys = getClosesRemoteKey(state)
                remoteKeys?.nextKey?.minus(1) ?: 0
            }
            LoadType.APPEND -> {
                val remoteKeys = getLastRemoteKey(state)
                    ?: throw InvalidObjectException("Remote key should not be null for $loadType")
                remoteKeys.nextKey
            }
            LoadType.PREPEND -> {
                val remoteKeys = getFirstRemoteKey(state)
                    ?: throw InvalidObjectException("Invalid state, key should not be null")
                remoteKeys.prevKey ?: return MediatorResult.Success(endOfPaginationReached = true)
                remoteKeys.prevKey
            }
        }
    }

    private suspend fun getFirstRemoteKey(state: PagingState<Int, PostEntity>): RemoteKeys? {
        return state.pages.firstOrNull {
            it.data.isNotEmpty()
        }
            ?.data?.firstOrNull()
            ?.let { postEntity -> db.remoteKeysDao.remoteKeysByRepoId(postEntity.id) }
    }

    private suspend fun getLastRemoteKey(state: PagingState<Int, PostEntity>): RemoteKeys? {
        return state.pages.lastOrNull {
            it.data.isNotEmpty()
        }?.data?.lastOrNull()
            ?.let { postEntity -> db.remoteKeysDao.remoteKeysByRepoId(postEntity.id) }
    }

    private suspend fun getClosesRemoteKey(state: PagingState<Int, PostEntity>): RemoteKeys? {
        return state.anchorPosition?.let { position ->
            state.closestItemToPosition(position)?.keyId?.let { repoId ->
                db.remoteKeysDao.remoteKeysByRepoId(repoId)
            }
        }
    }
}